﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using JXT.PrimaryKey.Batman.Reflection;

namespace JXT.PrimaryKey.Batman.ObjectConverter
{
    /// <summary>
    /// 包含自动转换功能的转换器基类
    /// </summary>
    /// <typeparam name="TSource">源类型</typeparam>
    /// <typeparam name="TResult">目标类型</typeparam>
    /// <typeparam name="TConverter">转换器类型</typeparam>
    public abstract class AutoConvertableBase<TSource, TResult, TConverter> : IConvertable<TSource, TResult>
        where TConverter : IConvertable<TSource, TResult>, new()
    {
        private static readonly Func<IObjectConverter<TSource, TResult>> _defaultConverterCreater =
            () => new ExpressionObjectConverter<TSource, TResult>();
        private static readonly Func<IObjectConverter<TResult, TSource>> _defaultBackConverterCreater =
            () => new ExpressionObjectConverter<TResult, TSource>();

        private static readonly TConverter _instanse = new TConverter();
        public static TConverter Instanse
        {
            get { return _instanse; }
        }

        private Func<IObjectConverter<TSource, TResult>> _converterCreater;
        private Func<IObjectConverter<TResult, TSource>> _backConverterCreater;

        private static readonly object _synLock = new object();

        private IObjectConverter<TSource, TResult> _converter;
        public IObjectConverter<TSource, TResult> Converter
        {
            get
            {
                if (_converter == null)
                {
                    lock (_synLock)
                    {
                        if (_converter == null)
                        {
                            _converter = _converterCreater();
                            BuildExpressionConverter(_converter);
                        }
                    }
                }
                return _converter;
            }
        }

        private IObjectConverter<TResult, TSource> _backConverter;
        public IObjectConverter<TResult, TSource> BackConverter
        {
            get
            {
                if (_backConverter == null)
                {
                    lock (_synLock)
                    {
                        if (_backConverter == null)
                        {
                            _backConverter = _backConverterCreater();
                            BuildExpressionBackConverter(_backConverter);
                        }
                    }
                }
                return _backConverter;
            }
        }

        public AutoConvertableBase(Func<IObjectConverter<TSource, TResult>> converterCreater,
            Func<IObjectConverter<TResult, TSource>> backConverterCreater)
        {
            _converterCreater = converterCreater;
            _backConverterCreater = backConverterCreater;
        }

        public AutoConvertableBase()
            : this(_defaultConverterCreater, _defaultBackConverterCreater)
        {
        }

        public TResult Convert(TSource source)
        {
            return Converter.Convert(source);
        }

        public TSource ConvertBack(TResult result)
        {
            return BackConverter.Convert(result);
        }

        public IEnumerable<TResult> ConvertEnumerable(IEnumerable<TSource> sourceEnumerable)
        {
            return sourceEnumerable.Select(e => Convert(e));
        }

        public IEnumerable<TSource> ConvertBackEnumerable(IEnumerable<TResult> resultEnumerable)
        {
            return resultEnumerable.Select(e => ConvertBack(e));
        }

        protected virtual void BuildExpressionConverter(IObjectConverter<TSource, TResult> converter)
        {
            Type sourceType = typeof(TSource);
            Type resultType = typeof(TResult);
            var sourceDictionary = sourceType.GetMetadataCacheDictionary();
            var metaItems = resultType.GetMetadataCacheItems();
            foreach (var item in metaItems)
            {
                LambdaReflectionMetadate sourceMeta;
                //默认转换属性名和类型一致的属性
                if (sourceDictionary.TryGetValue(item.Name, out sourceMeta))
                {
                    if (item.Property.Property.PropertyType == sourceMeta.Property.Property.PropertyType)
                    {
                        var sourceExpression = createPropertyExpression(sourceType, sourceMeta.Property.Property);
                        var resultExpression = createPropertyExpression(resultType, item.Property.Property);
                        converter.Add(resultExpression, sourceExpression);
                    }
                }
            }
        }

        protected virtual void BuildExpressionBackConverter(IObjectConverter<TResult, TSource> backConverter)
        {
            Type sourceType = typeof(TSource);
            Type resultType = typeof(TResult);
            var resultDictionary = resultType.GetMetadataCacheDictionary();
            var metaItems = sourceType.GetMetadataCacheItems();
            foreach (var item in metaItems)
            {
                LambdaReflectionMetadate resultMeta;
                //默认转换属性名和类型一致的属性
                if (resultDictionary.TryGetValue(item.Name, out resultMeta))
                {
                    if (item.Property.Property.PropertyType == resultMeta.Property.Property.PropertyType)
                    {
                        var resultExpression = createPropertyExpression(resultType, resultMeta.Property.Property);
                        var sourceExpression = createPropertyExpression(sourceType, item.Property.Property);
                        backConverter.Add(sourceExpression, resultExpression);
                    }
                }
            }
        }

        private static Expression createPropertyExpression(Type type, PropertyInfo property)
        {
            ParameterExpression parameter = Expression.Parameter(type, "e");
            return Expression.Property(parameter, property);
        }
    }
}
